公開されたPiping Serverを知っている人のみが使えるように限定公開する
やりたいこと
Piping Serverはネット上に公開してどんな場所からも使えるようにしたいけど、みんなが使えるようにするとサーバーの負荷が心配という時に使える。 特定のパスしか転送に使えない。
拒否された場合は、あたかもNginxが500 Internal Server Errorを起こしたようになる。 https://gh-card.dev/repos/nwtgck/rich-piping-server.svg https://github.com/nwtgck/rich-piping-server
使い方は、まず以下のようなconfig.yamlを書く。
code:config.yaml
allowPaths:
- /0s6twklxkrcfs1u
- type: regexp
basicAuthUsers:
- username: user1
password: pass1234
rejection: nginx-down
(basicAuthUsersは必須フィールドではないのでなくすとBasic認証なしでもできる。詳しいconfigに関しては後述) code:bash
npx nwtgck/hidden-piping-server --config-yaml-path=config.yaml
config.yamlはホットリロードされるようになっていて設定を変えれば自動でロードされて反映される。 上記の設定だと/0s6twklxkrcfs1uや正規表現でマッチする/aacacdbなどで転送は使えるがそれ以外だとNginxが500 Internal Server Error起こしたようなページが出現する。おそらくここにユーザーが訪れても「サーバー落ちてるな笑」ぐらいの気持ちで通り過ぎてくれると思う。 https://gyazo.com/d9d40a41f32cffff9cfbf824c8f3d647
rejectionをnginx-downの代わりにsocket-closeにするとNginxのエラー画面にならずにソケットを閉じた時の状態になり、以下のような画面になる。 https://gyazo.com/384f45faff2440e41ef9d9bce5eeaedf
dockerコマンドがあれば$PWDにconfig.yamlを用意して以下のコマンドをたたくだけでlocalhost:8181に立ち上がる。 code:bash
docker run -p 8181:8080 -v $PWD/config.yaml:/config.yaml nwtgck/rich-piping-server --config-yaml-path=/config.yaml
技術的な話
以下のio-tsを使って設定ファイルのバリデーションをしている。 https://gh-card.dev/repos/gcanti/io-ts.svg https://github.com/gcanti/io-ts
実際には以下のようにしてバリデーション用の値を作っている。configTypeはオブジェクトだがt.TypeOf<typeof configType>することでTypeScriptの型が作れることがポイント。 code:ts
...
export const configType = t.type({
basicAuthUsers: t.union([
t.array(t.type({
username: t.string,
password: t.string,
})),
t.undefined
]),
allowPaths: t.array(
t.union([
t.string,
t.type({
type: t.literal('regexp'),
value: t.string,
})
])
),
rejection: rejectionType,
});
export type Config = t.TypeOf<typeof configType>;
TypeScriptの型システムの恩恵を受けられてとても安心して開発できる。実際に以下のようにtype Configは導出される。 https://gyazo.com/debee292d070d53ac870f0786a159f53
Nginxの500 Internal Server Errorを模倣する https://gh-card.dev/repos/nwtgck/http-knocking.svg https://github.com/nwtgck/http-knocking
実際のコードは以下のようになる。ポイントはIEとGoogle Chromeは異なるレスポンスになる現象を発見したのでそれも組み込んであること。上記のconfig.yamlでも好きなNginxのバージョンをでき、その時代のリアルなNginxのエラーが出現させられると思う。本物とNginxと多少の差異はあると思うので、何か見つけたら寄せていきたい。 code:ts
import * as http from "http";
import * as http2 from "http2";
import * as useragent from "useragent";
export function fakeNginxResponse(res: http.ServerResponse | http2.Http2ServerResponse, nginxVersion: string, userAgent: string): void {
// (INFO: Ruby one-liner(localhost:8181 is an actual Nginx Server): puts curl -i -H 'User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1)' localhost:8081.split("\r\n").map{|e| (e+"\r\n").inspect}.join(" +\n")
const body =
"<html>\r\n" +
"<head><title>500 Internal Server Error</title></head>\r\n" +
"<body bgcolor=\"white\">\r\n" +
"<center><h1>500 Internal Server Error</h1></center>\r\n" +
<hr><center>nginx/${nginxVersion}</center>\r\n +
"</body>\r\n" +
"</html>\r\n" +
(
useragent.is(userAgent).ie || useragent.is(userAgent).chrome ?
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" +
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" +
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" +
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" +
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" +
"<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n" :
""
);
if ("shouldKeepAlive" in res) {
res.shouldKeepAlive = false;
}
res.writeHead(500, "Internal Server Error", {
"Server": nginx/${nginxVersion},
"Content-Type": "text/html",
"Content-Length": body.length
});
res.end(body);
}
例えば、以下のようにリクエストハンドラを作ると/hogepathでないと転送できないようにできる。
code:ts
import {Server as PipingServer} from "piping-server";
function generateHandler({pipingServer, useHttps}: {pipingServer: PipingServer, useHttps: boolean}): Handler {
const pipingHandler = pipingServer.generateHandler(useHttps);
return (req, res) => {
if (req.url !== "/hogepath") {
req.socket.end();
return;
}
pipingHandler(req, res);
};
}
上記のハンドラを使うには以下のようにNode.jsの標準のハンドラに組み込むことができる。 code:ts
import * as log4js from "log4js";
import * as piping from "piping-server";
// Create a piping server
const pipingServer = new piping.Server(logger);
http.createServer(generateHandler({pipingServer, useHttps: false}))
.listen(8080);
code:ヘルプ
Options:
--version Show version number boolean --https-port Port of HTTPS server number --key-path Private key path string --crt-path Certification path string useHttpsとは
useHttpsはPiping Serverの/helpでhttp://を使うかhttps://を使うか判定するために使っているものでPiping Serverのロジックとは無関係もの。(/helpページは動的にプロトコルやホストなどを埋め込んでなるべくユーザーに優しいヘルプになるようにしている。) 今後